/***************************************************************************************************
 * Copyright (c) 2017-2020, NVIDIA CORPORATION.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 *modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright notice,
 *this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *notice, this list of conditions and the following disclaimer in the
 *documentation and/or other materials provided with the distribution.
 *     * Neither the name of the NVIDIA CORPORATION nor the names of its
 *contributors may be used to endorse or promote products derived from this
 *software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY DIRECT,
 *INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 *OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TOR (INCLUDING
 *NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 *EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 **************************************************************************************************/
/* \file
   \brief Execution environment
*/

#include <iostream>
#include <stdexcept>

// Profiler includes
#include "cutlass_profiler.h"
#include "gemm_operation_profiler.h"
#include "conv2d_operation_profiler.h"
#include "conv3d_operation_profiler.h"
#include "sparse_gemm_operation_profiler.h"

/////////////////////////////////////////////////////////////////////////////////////////////////

namespace cutlass {
namespace profiler {

/////////////////////////////////////////////////////////////////////////////////////////////////

CutlassProfiler::CutlassProfiler(Options const& options) : options_(options) {
    operation_profilers_.emplace_back(new GemmOperationProfiler(options));

    operation_profilers_.emplace_back(new SparseGemmOperationProfiler(options));

    operation_profilers_.emplace_back(new Conv2dOperationProfiler(options));

    operation_profilers_.emplace_back(new Conv3dOperationProfiler(options));
}

CutlassProfiler::~CutlassProfiler() {}

/////////////////////////////////////////////////////////////////////////////////////////////////

/// Execute the program
int CutlassProfiler::operator()() {
    if (options_.about.help) {
        if (options_.operation_kind == library::OperationKind::kInvalid) {
            print_usage_(std::cout);
        } else {
            for (auto& profiler : operation_profilers_) {
                if (profiler->kind() == options_.operation_kind) {
                    profiler->print_usage(std::cout);
                    profiler->print_examples(std::cout);
                    return 0;
                }
            }
        }
        return 0;
    } else if (options_.about.version) {
        options_.about.print_version(std::cout);

        std::cout << std::endl;
        return 0;
    } else if (options_.about.device_info) {
        options_.device.print_device_info(std::cout);
        return 0;
    }

    if (options_.execution_mode == ExecutionMode::kProfile ||
        options_.execution_mode == ExecutionMode::kDryRun ||
        options_.execution_mode == ExecutionMode::kTrace) {
        // Profiles all operations
        profile_();
    } else if (options_.execution_mode == ExecutionMode::kEnumerate) {
        // Enumerates all operations
        enumerate_();
    }

    return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////////////

/// Enumerates all operations
void CutlassProfiler::enumerate_() {}

/// Profiles all operations
int CutlassProfiler::profile_() {
    int result = 0;
    DeviceContext device_context;

    // For all profilers
    for (auto& profiler : operation_profilers_) {
        if (options_.operation_kind == library::OperationKind::kInvalid ||
            options_.operation_kind == profiler->kind()) {
            result = profiler->profile_all(options_,
                                           library::Singleton::get().manifest,
                                           device_context);

            if (result) {
                return result;
            }
        }
    }

    return result;
}

/////////////////////////////////////////////////////////////////////////////////////////////////

/// Prints all options
void CutlassProfiler::print_usage_(std::ostream& out) {
    options_.print_usage(out);

    out << "\nOperations:\n\n";

    // For all profilers
    for (auto& profiler : operation_profilers_) {
        std::string kind_str = library::to_string(profiler->kind());

        size_t kAlignment = 40;
        size_t columns = 0;

        if (kind_str.size() < kAlignment) {
            columns = kAlignment - kind_str.size();
        }

        out << "     " << kind_str << std::string(columns, ' ')
            << profiler->description() << "\n";
    }

    out << "\n\nFor details about a particular function, specify the function "
           "name with --help.\n\nExample:\n\n"
        << "  $ cutlass_profiler --operation=Gemm --help\n\n"
        << "  $ cutlass_profiler --operation=Conv3d --help\n\n"
        << "  $ cutlass_profiler --operation=Conv2d --help\n\n";
}

/// Prints usage
void CutlassProfiler::print_options_(std::ostream& out) {
    options_.print_options(out);
}

/////////////////////////////////////////////////////////////////////////////////////////////////

/// Initializes the CUDA device
void CutlassProfiler::initialize_device_() {
    cudaError_t result = cudaSetDevice(options_.device.device);

    if (result != cudaSuccess) {
        std::cerr << "Failed to set device.";
        throw std::runtime_error("Failed to set device");
    }
}

/////////////////////////////////////////////////////////////////////////////////////////////////

}  // namespace profiler
}  // namespace cutlass

/////////////////////////////////////////////////////////////////////////////////////////////////
